package ss.pku.jsa.type.inference;

import java.util.Map;
import java.util.Set;

import ss.pku.jsa.type.constraint.solver.QError;


@SuppressWarnings({ "unchecked", "rawtypes" })
public class Constraint implements ConstraintJob {
	ObjectQType from;
	ObjectQType to;
	Edge edge;
	QualGate qgate;
	Location loc;
	int polarity;
	QError error;
	Kind kind;
	public static final int POLARITY_NEG = -1;
	public static final int POLARITY_NONE = 0;
	public static final int POLARITY_POS = 1;
	public static final int POLARITY_SUB = 2;
	final int x = 18;

	public Constraint(ObjectQType left, ObjectQType right, QualGate qgate,
			Location loc, int polarity, QError error, Kind kind) {
		this.from = left;
		this.to = right;
		this.qgate = qgate;
		this.loc = loc;
		this.polarity = polarity;
		this.error = error;
	}

	public String toString() {
		String rel = "->";
		String pol = "";

		int l = this.loc.locIndex;

		if (this.polarity == -1)
			pol = " (" + l;
		if (this.polarity == 1)
			pol = " )" + l;

		String edge = rel + pol + this.qgate + " " + this.loc + " "
				+ this.error;

		return this.from.getName() + " -> " + this.to.getName() + "  { " + edge
				+ " }";
	}

	public boolean equals(Object o) {
		if (!(o instanceof Constraint))
			return false;
		Constraint c = (Constraint) o;

		return (this.from.equals(c.from))
				&& (this.to.equals(c.to))
				&& (this.qgate.equals(c.qgate))
				&& ((this.polarity == 0) || ((this.loc == c.loc) && (this.polarity == c.polarity)))
				&& (this.kind == c.kind);
	}

	public int hashCode() {
		int ret = this.from.hashCode() + this.to.hashCode()
				+ this.qgate.hashCode();
		if (this.polarity != 0) {
			ret += this.polarity + this.loc.hashCode();
		}
		return ret;
	}

	public void moveSource(ObjectQType o) {
		this.from.getConstraints().remove(this);

		this.from = o;

		this.from.getConstraints().add(this);

		JqualPlugin.analysis.constraintWorklist.push(this);
	}

	public void moveTarget(ObjectQType o) {
		this.to.getConstraints().remove(this);

		this.to = o;

		this.to.getConstraints().add(this);

		JqualPlugin.analysis.constraintWorklist.push(this);
	}

	static void addConstraint(QType from, QType to, QualGate qgate,
			Location loc, int pol, QError error, Kind kind) {
		if ((from == null) || (to == null)) {
			return;
		}
		if (from.equals(to)) {
			return;
		}

		if ((from instanceof RefQType)) {
			if (!(to instanceof RefQType)) {
				throw new IllegalArgumentException("Constraint between ref "
						+ from + " and non-ref " + to + " at " + loc);
			}
			CQual.makeEdge(Kind.LT, from.q, to.q, qgate, loc, pol, error);
			RefQType rf = (RefQType) from;
			RefQType rt = (RefQType) to;

			if ((JqualPlugin.allowMerge) && (pol == 0)) {
				Profile.logMemStart("addConstraint/new MergeJob");
				MergeJob.addMergeJob(rt.deref(), rf.deref(), loc, error);
				Profile.logMemEnd();
			} else {
				addConstraint(rf.deref(), rt.deref(), qgate, loc, pol, error,
						kind);
				addConstraint(rt.deref(), rf.deref(), qgate, loc, revPol(pol),
						error, kind);
			}

		} else if (((from instanceof ObjectQType))
				&& ((to instanceof ObjectQType))) {
			ObjectQType f = ((ObjectQType) from).getObjEcr();
			ObjectQType t = ((ObjectQType) to).getObjEcr();

			if ((pol == 0) && (f.equals(t))) {
				return;
			}
			if ((JqualPlugin.alwaysMergeObjects) && (pol == 0)) {
				Profile.logMemStart("addConstraint/new MergeJob (2)");
				MergeJob.addMergeJob(from, to, loc, error);
				Profile.logMemEnd();
				return;
			}

			Profile.logMemStart("addConstraint/new Constraint");

			Constraint c = new Constraint(f, t, qgate, loc, pol, error, kind);
			Profile.logMemEnd();
			Set fConstraints = f.getConstraints();
			Set tConstraints = t.getConstraints();

			if (fConstraints.contains(c)) {
				return;
			}
			if (JqualPlugin.showContraintMsgs) {
				JqualPlugin.out.println("Adding " + c);
			}
			CQual.makeEdge(Kind.LT, from.q, to.q, qgate, loc, pol, error);

			fConstraints.add(c);
			tConstraints.add(c);

			JqualPlugin.analysis.constraintWorklist.push(c);
		} else if (((from instanceof PrimitiveQType))
				&& ((to instanceof PrimitiveQType))) {
			CQual.makeEdge(Kind.LT, from.q, to.q, qgate, loc, pol, error);
		} else {
			throw new IllegalArgumentException(
					"Constraint between incompatible qtypes " + from + "("
							+ from.getClass() + ") and " + to + "("
							+ to.getClass() + ") at " + loc);
		}
	}

	static int revPol(int pol) {
		switch (pol) {
		case 0:
			return 0;
		case -1:
			return 1;
		case 1:
			return -1;
		}
		throw new IllegalArgumentException("Unknown polarity type: " + pol);
	}

	public void apply() {
		if ((this.polarity == 0) && (this.from.equals(this.to))) {
			return;
		}
		applyToFields(this.from, this.to, false);
		applyToFields(this.to, this.from, true);
	}

	private boolean applyToFields(ObjectQType fieldSourceObject,
			ObjectQType fieldTargetObject, boolean reverse) {
		if ((fieldSourceObject instanceof ClassQType)) {
			return false;
		}

		for (Map.Entry e : fieldTargetObject.getFieldQTypes().entrySet()) {
			FieldKey key = (FieldKey) e.getKey();
			RefQType toField = (RefQType) e.getValue();

			if ((toField.isStatic) || (!key.treatFieldSensitively())) {
				continue;
			}

			RefQType fromField = fieldSourceObject.lookupFieldQType(key,
					toField.isStatic);
			if (fromField != null) {
				continue;
			}
			if (!JqualPlugin.analysis.isLegalField(fieldSourceObject.getType(),
					key)) {
				continue;
			}
			Profile.logMemStart("Constraint.applyToFields()/RefQType.copy()");
			fromField = toField.copy(this.loc);
			Profile.logMemEnd();
			fieldSourceObject.addFieldQType(key, fromField);

			if (reverse)
				addConstraint(fromField, toField, this.qgate, this.loc,
						revPol(this.polarity), this.error, Kind.LT);
			else {
				addConstraint(fromField, toField, this.qgate, this.loc,
						this.polarity, this.error, Kind.LT);
			}
		}

		return true;
	}

	public static enum Kind {
		EQ, LT;
	}

	class Edge {
		QualGate qgate;
		Location loc;
		int polarity;
		String error;

		public Edge(Constraint paramConstraint, QualGate qgate, Location loc,
				int polarity, String error) {
			this.qgate = qgate;
			this.loc = loc;
			this.polarity = polarity;
			this.error = error;
		}

		public boolean equals(Object o) {
			if (!(o instanceof Edge)) {
				return false;
			}
			Edge e = (Edge) o;

			if (this.polarity == 0) {
				return this.qgate.equals(e.qgate);
			}

			return (this.loc == e.loc) && (this.polarity == e.polarity)
					&& (this.qgate == e.qgate);
		}

		public int hashCode() {
			return this.loc.hashCode() + this.polarity + this.qgate.hashCode();
		}

		public String toString() {
			String rel = "->";
			String pol = "";

			int l = this.loc.locIndex;

			if (this.polarity == -1)
				pol = " (" + l;
			if (this.polarity == 1)
				pol = " )" + l;

			return rel + pol + this.qgate + " " + this.loc + " "
					+ this.error;
		}
	}

}
